extern "C"
{
#include "oncrpc-wrapper.h"
}

#include "rfc1813.h"

#include <cassert>
#include <cstdio>
#include <map>
#include <string>
//#include <vector>

///////////////////////////////////////////////////////////////////////////////
// RFC 1813

const USHORT MOUNT_PORT = 635;

///////////////////////////////////////////////////////////////////////////////

//typedef std::vector<char> buffer_t;
typedef size_t hash_t;
typedef std::map<hash_t, std::string> HandleMap;

///////////////////////////////////////////////////////////////////////////////

HandleMap g_handles;

///////////////////////////////////////////////////////////////////////////////

static BOOL createNewHandle(const char *path, fhandle3 *h)
{
  std::tr1::hash<std::string> hashString;
  BOOL ret = FALSE;

  const std::string str(path);
  const hash_t k = 1; //hashString(str);

  {
    const auto v = HandleMap::value_type(k, str);
    auto res = g_handles.insert(v);
    assert(res.second);
  }

  {
    const char *p = reinterpret_cast<const char *>(&k);
    std::copy(p, p + sizeof(k), h->begin());
  }

  return TRUE;
}

static BOOL umount(const char *path)
{
  std::tr1::hash<std::string> hashString;
  BOOL ret = FALSE;

  const std::string str(path);
  const hash_t k = 1; //hashString(str);
  auto it = g_handles.find(k);
  assert(g_handles.end() != it);
  g_handles.erase(it);

  return TRUE;
}

static bool_t xdr_mnt_args(XDR *xdrs, dirpath *dpath)
{
  bool_t res = FALSE;

  ULONG len = 0;
  res = wnfs_xdr_u_long(xdrs, &len);
  assert(res);

  char *buf = dpath->data();
  assert(len < MNTPATHLEN);
  buf[len] = '\0';
  res = wnfs_xdr_opaque(xdrs, &buf[0], len);
  assert(res);

  return TRUE;
}

static bool_t xdr_umnt_args(XDR *xdrs, dirpath *dpath)
{
  bool_t res = FALSE;

  ULONG len = 0;
  res = wnfs_xdr_u_long(xdrs, &len);
  assert(res);

  char *buf = dpath->data();
  assert(len < MNTPATHLEN);
  buf[len] = '\0';
  res = wnfs_xdr_opaque(xdrs, &buf[0], len);
  assert(res);

  return TRUE;
}

static bool_t xdr_mnt_res(XDR *xdrs, mountres3 *ret)
{
  assert(ret);
  bool_t res;

  {
    // status
    ULONG st = ret->fhs_status;
    res = wnfs_xdr_u_long(xdrs, &st);
    assert(res);
  }

  if (MNT3_OK == ret->fhs_status)
  {
    {
      // fhandle3
      ULONG len = ret->mountinfo.fhandle.size();
      res = wnfs_xdr_u_long(xdrs, &len);
      assert(res);

      auto ptr = ret->mountinfo.fhandle.data();
      u_int n = len;
      res = wnfs_xdr_bytes(xdrs, &ptr, &n, ret->mountinfo.fhandle.max_size());
      assert(res);
    }

    {
      // Auth flavors
      ULONG len = ret->mountinfo.auth_flavors.size();
      res = wnfs_xdr_u_long(xdrs, &len);
      assert(res);

      std::for_each(
        ret->mountinfo.auth_flavors.begin(), 
        ret->mountinfo.auth_flavors.end(),
        [&xdrs] (int v) { bool_t res = wnfs_xdr_int(xdrs, &v); assert(res); });
    }
  }

  return res;
}

void mountMnt(SVCXPRT *xprt)
{
  dirpath dpath;
  bool_t res = wnfs_svc_getargs(xprt, xdr_mnt_args, &dpath);
  assert(res);

  printf("%s: mounting '%s'\n", __FUNCTION__, dpath.data());
  fhandle3 fh;
  fh.fill(0);
  res = createNewHandle(dpath.data(), &fh);
  assert(res);

  mountres3 ret;
  ret.fhs_status = MNT3_OK;
  ret.mountinfo.fhandle = fh;
  ret.mountinfo.auth_flavors.push_back(AUTH_NONE);

  wnfs_svc_sendreply(xprt, xdr_mnt_res, &ret);
}

void mountUmnt(SVCXPRT *xprt)
{
  dirpath dpath;
  bool_t res = wnfs_svc_getargs(xprt, xdr_umnt_args, &dpath);
  assert(res);

  printf("%s: unmounting '%s'\n", __FUNCTION__, dpath.data());
  res = umount(dpath.data());
  assert(res);
}

void mountDispatch(struct svc_req *req, SVCXPRT *xprt)
{
  printf("%s: proc=%lu\n", __FUNCTION__, req->rq_proc);
  switch (req->rq_proc) 
  {
  case MOUNTPROC3_NULL:
    wnfs_svc_sendreply(xprt, xdr_void, NULL);
    break;

  case MOUNTPROC3_MNT:
    mountMnt(xprt);
    break;

  case MOUNTPROC3_UMNT:
    mountUmnt(xprt);
    break;

  default:
    printf("%s: Unknown proc (%lu)\n", __FUNCTION__, req->rq_proc);
    assert(FALSE);
  }
}

BOOL mountStart ()
{
  int res;
  int sock;
  struct sockaddr_in addr;
  SVCXPRT *xprt;
  const int len = sizeof(struct sockaddr_in);

  sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
  if (INVALID_SOCKET == sock) 
  {
    char msg[]="cannot create socket";
    printf("%s\n",msg);
    return FALSE;
  }

  addr.sin_addr.s_addr = 0;
  addr.sin_family = AF_INET;
  addr.sin_port = htons(MOUNT_PORT);
  res = bind(sock, (struct sockaddr *)&addr, len);
  if (0 != res) 
  {
    char msg[]="cannot bind";
    printf("%s\n",msg);
    return FALSE;
  }

  xprt = wnfs_svcudp_create(sock);
  if (nullptr == xprt) 
  {
    char msg[]="couldn't do udp_create";
    printf("%s\n",msg);
    return FALSE;
  }

  //res = wnfs_svc_register(xprt, MOUNT_PROGRAM, MOUNT_V3, mountDispatch, IPPROTO_UDP);
  //assert(res);

  sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if (0 > sock) 
  {
    char msg[]="cannot create socket";
    printf("%s\n",msg);
    return FALSE;
  }

  res = bind(sock, (struct sockaddr *)&addr, len);
  if (0 != res) 
  {
    char msg[]="cannot bind";
    printf("%s\n",msg);
    return FALSE;
  }

  xprt = wnfs_svctcp_create(sock, 0, 0);
  if (nullptr == xprt) 
  {
    char msg[]="couldn't do tcp_create";
    printf("%s\n",msg);
    return FALSE;
  }

  return wnfs_svc_register(xprt, MOUNT_PROGRAM, MOUNT_V3, mountDispatch, IPPROTO_TCP);
}
